#include "lmn_json.h"
#include "rule.h"
#include "membrane.h"
#include "atom.h"
#include "symbol.h"
#include "functor.h"
#include "special_atom.h"
#include "util.h"
#include "error.h"
#include "json.h"
#include "string.h"
#include "ccallback.h"
#include "slim_header/memstack.h"
#include "utility/visitlog.h"

#define wait_parent() getchar()

#define json_object_object_foreach_ex(obj, key, val) char *key; JsonObject *val; struct lh_entry *entry; \
  for(entry = json_object_get_object(obj)->head; ({ if(entry) { key = (char*)entry->k; val = (JsonObject*)entry->v; } ; entry; }); entry = entry->next )

typedef struct json_object JsonObject;

struct LmnString{
  LMN_SP_ATOM_HEADER;
  unsigned long buf_size, len;
  char *buf;
};

static ProcessTbl removed_table;
static ProcessTbl added_table;
static ProcessTbl modified_table;

static JsonObject* get_json_mem(LmnMembrane *mem);
static JsonObject* get_json_atom(LmnSAtom atom);
static void delete_json_object(JsonObject *obj);
static int each_proc_tbl_callback_f(LmnWord key, LmnWord val, LmnWord arg);

static void delete_json_object(JsonObject *obj){
  json_object_object_foreach_ex(obj, key, val){
    json_object_object_del(obj, key);
  }
  free(obj);
}

static int each_proc_tbl_callback_f(LmnWord key, LmnWord val, LmnWord arg){
  JsonObject *json_obj = (JsonObject *)val;
  JsonObject *diffs = (JsonObject *)arg;
  json_object_array_add(diffs, json_obj);
  return 1;
}

static JsonObject* get_json_mem(LmnMembrane *mem){
  AtomListEntry *ent;
  LmnFunctor f;
  LmnMembrane *m;
  JsonObject *me, *jmems, *jatoms;

  me = json_object_new_object();  
  jatoms = json_object_new_array();
  jmems = json_object_new_array();
  EACH_ATOMLIST_WITH_FUNC(mem, ent, f, ({
        LmnSAtom atom;
        if (LMN_IS_EX_FUNCTOR(f)) continue;
        EACH_ATOM(atom, ent, ({
          json_object_array_add(jatoms, get_json_atom(atom));
        }));
  }));

  for (m = mem->child_head; m; m = m->next) {
    json_object_array_add(jmems, get_json_mem(m));
  }
  json_object_object_add(me, "id", json_object_new_int((int)lmn_mem_id(mem)));
  json_object_object_add(me, "membranes", jmems);
  json_object_object_add(me, "atoms", jatoms);
  return me;
}

static JsonObject* get_json_atom(LmnSAtom atom){
  JsonObject *jatom, *jattrs, *jlinks;
  int arity = LMN_SATOM_GET_ARITY(atom);
  int i;
  jatom = json_object_new_object();
  json_object_object_add(jatom, "name", json_object_new_string(LMN_SATOM_STR(atom)));
  json_object_object_add(jatom, "id", json_object_new_int(LMN_SATOM_ID(atom)));
  jattrs = json_object_new_array();
  jlinks = json_object_new_array();
  
  for(i = 0; i < arity; i++){
    LmnSAtom a;
    struct LmnString *str;
    LmnLinkAttr attr = LMN_SATOM_GET_ATTR(atom, i);

    if(!LMN_ATTR_IS_DATA(attr)){
      attr = 0;
      a = LMN_SATOM(LMN_SATOM_GET_LINK(atom, i));
      if(a != NULL){
        json_object_array_add(jlinks, json_object_new_int((int)LMN_SATOM_ID(a)));
      }
    }
    json_object_array_add(jattrs, json_object_new_int((int)attr));

    switch(attr){
      case LMN_INT_ATTR:
        json_object_array_add(jlinks, json_object_new_int((int)LMN_SATOM_GET_LINK(atom, i)));
        break;
      case LMN_DBL_ATTR:
        json_object_array_add(jlinks, json_object_new_double(*(double*)LMN_SATOM_GET_LINK(atom, i)));
        break;
      case LMN_SP_ATOM_ATTR:
      case LMN_CONST_STR_ATTR:
        str = (struct LmnString *)LMN_SATOM_GET_LINK(atom, i);
        json_object_array_add(jlinks, json_object_new_string(str->buf));
        break;
      case LMN_CONST_DBL_ATTR:
        json_object_array_add(jlinks, json_object_new_double(*(double*)LMN_SATOM_GET_LINK(atom, i)));
        break;
      case LMN_HL_ATTR:
      default:
         break;
    }
  }
  json_object_object_add(jatom, "link_attrs", jattrs);
  json_object_object_add(jatom, "link_data", jlinks);
  return jatom;
}

void init_diffinfo(){
  if(removed_table != NULL){
    proc_tbl_clear(removed_table);
    proc_tbl_clear(added_table);
    proc_tbl_clear(modified_table);
  }
  removed_table = proc_tbl_make();
  added_table = proc_tbl_make();
  modified_table = proc_tbl_make();
}

void add_mem_to_diffinfo(LmnMembrane *mem, DiffNodeType type){
  JsonObject *jmem;
  jmem = get_json_mem(mem);

  switch(type){
  case ADDED_NODE:
    proc_tbl_put(added_table, lmn_mem_id(mem) , (LmnWord)jmem);
    break;
  case REMOVED_NODE:
    proc_tbl_put(removed_table, lmn_mem_id(mem) , (LmnWord)jmem);
    break;
  case MODIFIED_NODE:
    /* 追加されたノードだったら修正に入ってきた場合
     * 追加されたノードを更新する
     **/
    if (proc_tbl_contains(added_table,  lmn_mem_id(mem))) {
      LmnWord objaddr;
      JsonObject *obj;
      //削除処理
      proc_tbl_get(added_table,  lmn_mem_id(mem), (LmnWord*)(&objaddr));
      obj = (JsonObject *)objaddr;
      delete_json_object(obj);
      proc_tbl_put(added_table, lmn_mem_id(mem), (LmnWord)jmem);
    }else{
      proc_tbl_put(modified_table, lmn_mem_id(mem), (LmnWord)jmem);
    }
    break;
  }
}

void add_atom_to_diffinfo(LmnSAtom atom, DiffNodeType type){
  JsonObject *jatom;
  jatom = get_json_atom(atom);

  switch(type){
  case ADDED_NODE:
    proc_tbl_put(added_table, LMN_SATOM_ID(atom) , (LmnWord)jatom);
    break;
  case REMOVED_NODE:
    proc_tbl_put(removed_table, LMN_SATOM_ID(atom) , (LmnWord)jatom);
    break;
  case MODIFIED_NODE:
    /* 追加されたノードだったら修正に入ってきた場合
     * 追加されたノードを更新する
     **/
    if (proc_tbl_contains(added_table,  LMN_SATOM_ID(atom))) {
      LmnWord objaddr;
      JsonObject *obj;
      //削除処理
      proc_tbl_get(added_table,  LMN_SATOM_ID(atom), (LmnWord*)(&objaddr));
      obj = (JsonObject *)objaddr;
      delete_json_object(obj);
      proc_tbl_put(added_table, LMN_SATOM_ID(atom), (LmnWord)jatom);
    }else{
      proc_tbl_put(modified_table, LMN_SATOM_ID(atom), (LmnWord)jatom);
    }
    break;
  }
}

void dump_json(LmnMembrane *mem){
  AtomListEntry *ent;
  LmnFunctor f;
  LmnMembrane *m;
  char* json;

  JsonObject *slim_json_obj;

  JsonObject *root;
  JsonObject *jatoms;
  JsonObject *jmems;

  JsonObject *added_nodes;
  JsonObject *modified_nodes;
  JsonObject *removed_nodes;
  
  slim_json_obj = json_object_new_object();
  root = json_object_new_object();
  jatoms = json_object_new_array();
  jmems = json_object_new_array();
  
  added_nodes = json_object_new_array();
  modified_nodes = json_object_new_array();
  removed_nodes = json_object_new_array();

  //全体の構造
  EACH_ATOMLIST_WITH_FUNC(mem, ent, f, ({
        LmnSAtom atom;
        if (LMN_IS_EX_FUNCTOR(f)) continue;
        EACH_ATOM(atom, ent, ({
          json_object_array_add(jatoms, get_json_atom(atom));
        }));
  }));

  for (m = mem->child_head; m; m = m->next) {
    json_object_array_add(jmems, get_json_mem(m));
  }
  json_object_object_add(root, "id", json_object_new_int((int)lmn_mem_id(mem)));
  json_object_object_add(root, "membranes", jmems);
  json_object_object_add(root, "atoms", jatoms);

  json_object_object_add(slim_json_obj, "root_membrane", root);
 
  //差分情報
  proc_tbl_foreach(added_table, each_proc_tbl_callback_f, (LmnWord)(added_nodes));
  proc_tbl_foreach(removed_table, each_proc_tbl_callback_f, (LmnWord)(removed_nodes));
  proc_tbl_foreach(modified_table, each_proc_tbl_callback_f, (LmnWord)(modified_nodes));
  json_object_object_add(slim_json_obj, "modified_nodes", added_nodes);
  json_object_object_add(slim_json_obj, "added_nodes", modified_nodes);
  json_object_object_add(slim_json_obj, "removed_nodes", removed_nodes);

  //json出力
  json =  json_object_to_json_string(slim_json_obj);
  fprintf(stdout, "%s\n", json);
  fflush(stdout);

  delete_json_object(slim_json_obj);
  wait_parent();
  return;
}

